﻿#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stddef.h>


//************************************************第19讲 自定义类型：结构体**************************************************

//****************************************************************************************1 结构体类型的声明

//**********************1.1 结构体回顾
//书 ：书名，作者，价格，出版号

//结构体的定义
//struct Book
//{
//	char book_name[20];
//	char author[20];
//	float price;
//	char id[19];
//}b3, b4;
//
//int main()
//{
//	//结构体的初始化
//	//1.按顺序初始化
//	struct Book b1 = { "三体", "刘慈欣", 38.8, "LCX20240611" };
//	//2.按指定顺序初始化
//	struct Book b2 = { .author = "唐家三少", .book_name = "斗罗大陆", .id = "TJSS20240611", .price = 50 };
//
//	printf("%s %s %f %s\n", b1.book_name, b1.author, b1.price, b1.id);//结果 三体 刘慈欣 38.799999 LCX20240611
//																						//浮点数在内存中不一定是精确存储的  拓展：在进行浮点数的比较时，应考虑这个因素
//
//	return 0;
//}

//**********************1.2 结构体的特殊声明

//匿名结构体类型（省略掉了结构体标签）
//struct
//{
//	char c;
//	int i;
//	double d;
//}s1, s2;
//注意：匿名的结构体类型，如果没有对结构体类型重命名的话，基本上只能使⽤⼀次。

//struct
//{
//	char c;
//	int i;
//	double d;
//}s1;
//
//struct
//{
//	char c;
//	int i;
//	double d;
//}*ps;
//
//int main()
//{
//	ps = &s1;//编译器报错：从“*”到“*”的类型不兼容
//	//原因：由于是匿名结构体，尽管成员一样，但编译器仍会当成完全不同的两种类型
//	return 0;
//}

//**********************1.3 结构体的自引用

//定义一个链表的节点
//struct Node
//{
//	int date;//数据域  
//	struct Node* next;//指针域
//
//};

//typedef struct Node//typedef 对结构体重命名 
//{
//	int date;
//	Node* next;//错误的写法---要先创建再使用
//
//}Node;
//
//int main()
//{
//	return 0;
//}



//****************************************************************************************2 结构体内存对齐

//struct S1
//{
//	char c1;
//	char c2;
//	int n;
//};
//struct S2
//{
//	char c1;
//	int n;
//	char c2;
//};
//
//int main()
//{
//	//printf("%zd\n", sizeof(struct s1));//8
//	//printf("%zd\n", sizeof(struct s2));//12
//
//	//offsetof -- 宏  计算结构体成员相较于结构体变量起始位置的偏移量  头文件 <stddef.h>
//	struct S1 s1 = { 0 };
//	printf("%zd\n", offsetof(struct S1, c1));//0
//	printf("%zd\n", offsetof(struct S1, c2));//1
//	printf("%zd\n", offsetof(struct S1, n));//4
//	return 0;
//}

//**********************2.1 对齐规则
/*
  1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
  2. 其他成员变量要对⻬到某个数字（对⻬数）的整数倍的地址处。
	对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。
		-VS 中默认的值为 8
		-Linux中 gcc 没有默认对⻬数，对⻬数就是成员⾃⾝的⼤⼩
  3. 结构体总⼤⼩为最⼤对⻬数（结构体中每个成员变量都有⼀个对⻬数，所有对⻬数中最⼤的）的
     整数倍。
  4. 如果嵌套了结构体的情况，嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处，结构
     体的整体⼤⼩就是所有最⼤对⻬数（含嵌套结构体中成员的对⻬数）的整数倍。
*/ 

//struct S1
//{
//	char c1;
//	char c2;
//	int n;
//};
//struct S2
//{
//	char c1;
//	int n;
//	char c2;
//};
//
//struct S3
//{
//	double d;
//	char c;
//	int i;
//};
//
//struct S4
//{
//	char c1;
//	struct S3 s3;
//	double d;
//};
//
//int main()
//{
//	//printf("%zd\n", sizeof(struct s1));//8
//	//printf("%zd\n", sizeof(struct s2));//12
//
//	printf("%zd\n", sizeof(struct S3));//16
//	printf("%zd\n", sizeof(struct S4));//32
//
//
//	return 0;
//}

//**********************2.2 为什么存在内存对齐

//1 平台原因
//2 性能原因
// 总之：结构体内存对齐是用空间换取时间的做法

//**********************2.3 修改默认对齐数
//#pragma 修改默认对齐数


//#pragma pack(1)//设置默认对齐数为1
//struct S
//{
//	char c1;
//	int n;
//	char c2;
//};
//#pragma pack()//恢复为默认对齐数
//
////结构体在对⻬⽅式不合适的时候，我们可以⾃⼰更改默认对⻬数 --- 一般设为2的次方数
//
//int main()
//{
//	printf("%zd\n", sizeof(struct S));//6
//	return 0;
//}

//****************************************************************************************3 结构体传参
//struct S
//{
//	int arr[1000];
//	int n;
//	char ch;
//};
//
//void print1(struct S tmp)
//{
//	for (int i = 0; i < 10; i++)
//	{
//		printf("%d ", tmp.arr[i]);
//	}
//	printf("\n");
//	printf("n = %d\n", tmp.n);
//	printf("ch = %c\n", tmp.ch);
//}
//
//void print2(struct S* ps)
//{
//	for (int i = 0; i < 10; i++)
//	{
//		printf("%d ", ps->arr[i]);
//	}
//	printf("\n");
//	printf("n = %d\n", ps->n);
//	printf("ch = %c\n", ps->ch);
//}
//
//int main()
//{
//	struct S s = { { 1,2,3,4,5,6,7,8,9,10}, 10, 'a' };
//
//	//1 传值调用
//	print1(s);
//	//2 传址调用 --- 首选print2（传址调用）
//	print2(&s);
///*原因：
//	函数传参的时候，参数是需要压栈，会有时间和空间上的系统开销。
//	如果传递⼀个结构体对象的时候，结构体过⼤，参数压栈的的系统开销⽐较⼤，所以会导致性能的下
//	降*/
//}




//****************************************************************************************4 结构体实现位段
//**********************4.1 什么是位段
/* 
1. 位段的成员必须是 int、unsigned int 或signed int ，在C99中位段成员的类型也可以选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
 */
//struct S
//{
//	int _a;
//	int _b;
//	int _c;
//	int _d;
//
//};

//位段
//struct S
//{
//	int _a:2;
//	int _b:4;
//	int _c:20;
//	int _d:30;//即使满足条件，但也是有问题的（跨平台问题，比如 在16位操作系统）
//
//};
//
//int main()
//{
//	printf("%zd\n", sizeof(struct S));//8
//	return 0;
//}

//**********************4.2 位段的内存分配
/*
	1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
	2. 位段的空间上是按照需要以4个字节（ int ）或者1个字节（ char ）的⽅式来开辟的。
	3. 位段涉及很多不确定因素，位段是不跨平台的，注重可移植的程序应该避免使⽤位段。
*/
//**********************4.3 位段的跨平台问题

/*
	1. int 位段被当成有符号数还是⽆符号数是不确定的。
	2. 位段中最⼤位的数⽬不能确定。（16位机器最⼤16，32位机器最⼤32，写成27，在16位机器会
	出问题。
	3. 位段中的成员在内存中从左向右分配，还是从右向左分配，标准尚未定义。
	4. 当⼀个结构包含两个位段，第⼆个位段成员⽐较⼤，⽆法容纳于第⼀个位段剩余的位时，是舍弃
	剩余的位还是利⽤，这是不确定的。
*/


//**********************4.4 位段的应用
// ⽹络协议中中的IP数据报

//**********************4.5 位段使用的注意事项
/*
位段的⼏个成员共有同⼀个字节，这样有些成员的起始位置并不是某个字节的起始位置，那么这些位
置处是没有地址的。内存中每个字节分配⼀个地址，⼀个字节内部的bit位是没有地址的。

所以不能对位段的成员使⽤& 操作符，这样就不能使⽤scanf直接给位段的成员输⼊值，只能是先输⼊
放在⼀个变量中，然后赋值给位段的成员。
*/

//struct A
//{
//	int _a : 2;
//	int _b : 5;
//	int _c : 10;
//	int _d : 30;
//};
//int main()
//{
//	struct A sa = { 0 };
//	//scanf("%d", &sa._b);//这是错误的
//
//	//正确的⽰范
//	int b = 0;
//	scanf("%d", &b);
//	sa._b = b;
//	return 0;
//}


